package com.apobates.forum.utils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import java.util.zip.CRC32;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
/**
 * 公共工具类
 * 
 * @author xiaofanku
 * @since 20190301
 */
public final class Commons {
	public final static String Y = DateTimeUtils.Y;
	public final static String M = DateTimeUtils.M;
	public final static String D = DateTimeUtils.D;
	public final static String NEWLINE = "\r\n";
	private static final NavigableMap<Long, String> suffixes = new TreeMap<> ();
	
	static {
		suffixes.put(1_000L, "k");
		suffixes.put(1_000_000L, "M");
		suffixes.put(1_000_000_000L, "G");
		suffixes.put(1_000_000_000_000L, "T");
		suffixes.put(1_000_000_000_000_000L, "P");
		suffixes.put(1_000_000_000_000_000_000L, "E");
	}
	
	private Commons() throws Exception {
		throw new Exception("不需要实化公共工具类");
	}

	/**
	 * 使用Apache Commons Lang3 判断参数是否不为空<br/>
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isNotBlank(String str) {
		boolean data = StringUtils.isNotBlank(str);
		if (data) {
			data = !(str.toLowerCase().equals("null"));
		}
		return data;
	}

	/**
	 * sha256 散列<br/>
	 * 使用org.apache.commons.codec实现
	 * 
	 * @param unEncryptString
	 *            未加密的字符串
	 * @param salt
	 *            盐
	 * @return
	 */
	public static String sha256(String unEncryptString, String salt) {
		Objects.requireNonNull(unEncryptString);
		Objects.requireNonNull(salt);
		String data = salt + unEncryptString;
		return DigestUtils.sha256Hex(data);
	}

	/**
	 * md5 散列 使用org.apache.commons.codec实现
	 * 
	 * @param unEncryptString
	 *            未加密的字符串
	 * @return
	 */
	public static String md5(String unEncryptString) {
		Objects.requireNonNull(unEncryptString);
		return DigestUtils.md5Hex(unEncryptString);
	}
	/**
	 * 生成指定数量的随机数字<br/>
	 * 使用org.apache.commons.lang3实现
	 * 
	 * @param size
	 *            随机数的长度
	 * @return
	 */
	public static String randomNumeric(int size) {
		return RandomStringUtils.randomNumeric(size);
	}

	/**
	 * 生成指定数量的随机数字<br/>
	 * 使用org.apache.commons.lang3实现
	 * 
	 * @param minSize
	 *            随机数的最短长度
	 * @param maxSize
	 *            随机数的最长长度
	 * @return
	 */
	public static String randomNumeric(int minSize, int maxSize) {
		int size = RandomUtils.nextInt(minSize, maxSize);
		return RandomStringUtils.randomNumeric(size);
	}
	
	/**
	 * 在指定范围内随机取一个字符串
	 * 
	 * @param start  开始点
	 * @param length 最大值
	 * @return
	 */
	public static long randomNumeric(long start, long length){
		long seek = RandomUtils.nextLong(start, length);
		return seek;
	}
	
	/**
	 * 生成指定数量的数机字符,字母和数字<br/>
	 * 使用org.apache.commons.lang3实现
	 * 
	 * @param size
	 *            随机数的长度
	 * @return
	 */
	public static String randomAlphaNumeric(int size) {
		return RandomStringUtils.randomAlphanumeric(size);
	}

	/**
	 * 生成指定数量的数机字符,字母和数字<br/>
	 * 使用org.apache.commons.lang3实现
	 * 
	 * @param minSize
	 *            随机数的最短长度
	 * @param maxSize
	 *            随机数的最长长度
	 * @return
	 */
	public static String randomAlphaNumeric(int minSize, int maxSize) {
		int size = RandomUtils.nextInt(minSize, maxSize);
		return RandomStringUtils.randomAlphanumeric(size);
	}

	/**
	 * 返回手机号码的crc32值<br/>
	 * 使用java.util.zip.CRC32, 调用前确认与目标数据库支持的CRC32值是否一致
	 * 
	 * @param phone
	 *            手机号码
	 * @return long
	 */
	public static long crc32(String phone) {
		if (phone == null || phone.isEmpty()) {
			return 0L;
		}
		byte[] b = phone.getBytes(); // 将字符串转为字节数组
		CRC32 crc = new CRC32(); // 创建对象
		crc.update(b); // 更新校验字节数组
		return crc.getValue();
	}

	/**
	 * 判断指定的字符串是否是手机号
	 * 
	 * @param phone
	 *            字符串
	 * @return 采用正式匹配规则，数字判断，通过返回true,反之返回false
	 */
	public static boolean isPhoneNumber(String phone) {
		if (!StringUtils.isNumeric(phone)) {
			return false;
		}
		Matcher matcher = Pattern.compile("^[1][3,4,5,6,7,8,9][0-9]{9}$", Pattern.CASE_INSENSITIVE).matcher(phone);
		String mobile = null;
		while (matcher.find()) {
			mobile = matcher.group();
		}
		return mobile != null;
	}

	/**
	 * 验证邮箱地址
	 * 
	 * @see https://howtodoinjava.com/regex/java-regex-validate-email-address/
	 * @param mailString
	 *            邮箱地址
	 * @return
	 */
	public static boolean isMailString(String mailString) {
		if (!isNotBlank(mailString)) {
			return false;
		}
		String regex = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(mailString);
		return matcher.matches();
	}

	/**
	 * 获得用户远程地址<br/>
	 * getRemoteAddr的别名方法
	 * 
	 * @param request
	 * @return
	 */
	public static String getRequestIp(HttpServletRequest request) {
		return getRemoteAddr(request);
	}

	/**
	 * 获得用户远程地址
	 * 
	 * @param request
	 * @return
	 */
	public static String getRemoteAddr(HttpServletRequest request) {
		String remoteAddr = request.getHeader("X-Real-IP");
		if (isNotBlank(remoteAddr)) {
			remoteAddr = request.getHeader("X-Forwarded-For");
		} else if (isNotBlank(remoteAddr)) {
			remoteAddr = request.getHeader("Proxy-Client-IP");
		} else if (isNotBlank(remoteAddr)) {
			remoteAddr = request.getHeader("WL-Proxy-Client-IP");
		}
		return remoteAddr != null ? remoteAddr : request.getRemoteAddr();
	}

	/**
	 * 解析身份证出生年月日信息
	 * 
	 * @param identityCard
	 *            身份证
	 * @return 如果解析失败返回空集合 Key=Commons.Y 出生的年份 Key=Commons.M 出生的月份 Key=Commons.D
	 *         出生的日子
	 */
	public static Map<String, String> parseCitizenCard(String identityCard) {
		Citizen c = new Citizen(identityCard);
		if (!c.isWell()) {
			return Collections.emptyMap();
		}
		String tmp = c.getBirthday();
		Map<String, String> data = new HashMap<>();
		data.put(Commons.Y, tmp.substring(0, 4));
		data.put(Commons.M, tmp.substring(4, 6));
		data.put(Commons.D, tmp.substring(6));
		return data;
	}
	public static int stringToInteger(String value, int defaultValue){
		return stringToNumberic(value, val->Integer.valueOf(val.trim()), ()->defaultValue);
	}

	/**
	 * 
	 * @param <R>
	 * @param value
	 * @param mapper
	 * @param supplier
	 * @return
	 */
	private static <R> R stringToNumberic(String value, Function<String, R> mapper, Supplier<R> supplier) {
		try {
			return Optional.ofNullable(value).map(mapper).orElseGet(supplier);
		} catch (NumberFormatException e) {
			return supplier.get();
		}
	}
	public static long stringToLong(String value, long defaultValue){
		return stringToNumberic(value, val->Long.valueOf(val.trim()), ()->defaultValue);
	}
	/**
	 * 如果目标为空或null使用默认值<br/>
	 * 使用org.apache.commons.lang3实现
	 * 
	 * @param target
	 *            目标字符串值
	 * @param defaultValue
	 *            默认的值
	 * @return
	 */
	public static String optional(String target, String defaultValue) {
		return Optional.ofNullable(target).orElse(defaultValue);
	}

	/**
	 * 如果目标为空或null使用默认值
	 * 
	 * @param target
	 *            目标可能会为Null的供应函数
	 * @param defaultValue
	 *            默认的值
	 * @return
	 */
	public static String optional(Supplier<Object> target, String defaultValue) {
		try {
			return optional(target.get().toString(), defaultValue);
		} catch (NullPointerException e) {
			return defaultValue;
		}
	}
	/**
	 * 如果目标为空或null使用默认值<br/>
	 * 
	 * @param target       目标值
	 * @param defaultValue 默认值
	 * @return
	 */
	public static Long optional(Long target, Long defaultValue){
		if(null != target && target > 0){
			return target;
		}
		return defaultValue;
	}
	
	/**
	 * 如果目标为空或null使用默认值为0
	 * 
	 * @param target 目标值
	 * @return
	 */
	public static Long optionalOrZero(Long target){
		return optional(target, 0L);
	}
	/**
	 * 字符数组是否是空的,只要参数等于null或数组长度等于0返回false
	 * 
	 * @param strArray
	 * @return
	 */
	public static boolean isNotEmpty(String[] strArray) {
		return ArrayUtils.isNotEmpty(strArray);
	}

	/**
	 * 是否是数字字符串<br/>
	 * 只适用于正整数,不适用于小数位的字符串,不适用于负数<br/>
	 * 使用apache common lang3实现<br/>
	 * Checks if the CharSequence contains only Unicode digits. A decimal point
	 * is not a Unicode digit and returns false.
	 * 
	 * @param str
	 * @return
	 */
	public static boolean isNumeric(String str) {
		Objects.requireNonNull(str);
		return StringUtils.isNumeric(str);
	}

	/**
	 * 将字符串转换成安全的字符.只有纯文字,所有标签都被剔除<br/>
	 * 使用org.jsoup实现
	 * 
	 * @param htmlString
	 * @return
	 */
	public static String htmlPurifier(String htmlString) {
		Objects.requireNonNull(htmlString);
		return Jsoup.clean(htmlString, Whitelist.none());
	}

	/**
	 * 替掉不安全的html标签,最大程度的保留格式标签.可用的标签如下<br/>
	 * a, b, blockquote, br, caption, cite, code, col, colgroup, dd, div, dl,
	 * dt, em, h1, h2, h3, h4, h5, h6, i, img, li, ol, p, pre, q, small, span,
	 * strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, u,
	 * ul<br/>
	 * 使用org.jsoup实现<br/>
	 * 
	 * @param htmlString
	 * @return
	 */
	public static String htmlCleanUsedRelaxed(String htmlString) {
		Objects.requireNonNull(htmlString);
		return Jsoup.clean(htmlString, Whitelist.relaxed());
	}

	/**
	 * 将逗号分隔的数字字符串转成Long列表
	 * 
	 * @param idArrayString
	 * @return
	 */
	public static List<Long> toLongList(String idArrayString) {
		if(!Commons.isNotBlank(idArrayString)){
			return Collections.emptyList();
		}
		return Stream.of(idArrayString.split(",")).filter(s -> s.trim().length() > 0 && NumberUtils.isNumber(s.trim())).map(s -> Long.parseLong(s.trim())).distinct().collect(Collectors.toList());
	}

	public static Set<Long> toLongSet(String idArrayString) {
		if(!Commons.isNotBlank(idArrayString)){
			return Collections.emptySet();
		}
		return Stream.of(idArrayString.split(",")).filter(s -> s.trim().length() > 0 && NumberUtils.isNumber(s.trim())).map(s -> Long.parseLong(s.trim())).distinct().collect(Collectors.toSet());
	}

	/**
	 * 将数字字符串数组转成Long列表
	 * 
	 * @param idArray
	 * @return
	 */
	public static List<Long> toLongList(String[] idArray) {
		return Stream.of(idArray).filter(s->s.trim().length() > 0 && NumberUtils.isNumber(s.trim())).map(s -> Long.parseLong(s.trim())).distinct().collect(Collectors.toList());
	}

	/**
	 * 将逗号分隔的数字字符串转成Integer列表
	 * 
	 * @param idArrayString
	 * @return
	 */
	public static List<Integer> toIntegerList(String idArrayString) {
		if(!Commons.isNotBlank(idArrayString)){
			return Collections.emptyList();
		}
		return Stream.of(idArrayString.split(",")).filter(s->s.trim().length() > 0 && NumberUtils.isNumber(s.trim())).map(s -> Integer.parseInt(s.trim())).distinct().collect(Collectors.toList());
	}

	public static Set<Integer> toIntegerSet(String idArrayString) {
		if(!Commons.isNotBlank(idArrayString)){
			return Collections.emptySet();
		}
		return Stream.of(idArrayString.split(",")).filter(s->s.trim().length() > 0 && NumberUtils.isNumber(s.trim())).map(s -> Integer.parseInt(s.trim())).distinct().collect(Collectors.toSet());
	}

	/**
	 * 将数字字符串数组转成Integer列表
	 * 
	 * @param idArray
	 * @return
	 */
	public static List<Integer> toIntegerList(String[] idArray) {
		return Stream.of(idArray).filter(s->s.trim().length() > 0 && NumberUtils.isNumber(s.trim())).map(s -> Integer.parseInt(s.trim())).distinct().collect(Collectors.toList());
	}

	/**
	 * 将long数组转成Long列表
	 * 
	 * @param array
	 * @return
	 */
	public static List<Long> boxLongList(long[] array) {
		if (array == null || array.length == 0) {
			return Collections.emptyList();
		}
		return LongStream.of(array).boxed().collect(Collectors.toList());
	}

	/**
	 * 将int数组转成Integer列表
	 * 
	 * @param array
	 * @return
	 */
	public static List<Integer> boxIntegerList(int[] array) {
		if (array == null || array.length == 0) {
			return Collections.emptyList();
		}
		return IntStream.of(array).boxed().collect(Collectors.toList());
	}

	/**
	 * 查看指定目录下的所有子文件夹名称:若子文件夹中还有文件夹会继续遍历
	 * 
	 * @param folderPath
	 * @return 返回的元素值是路径格式,调用时需要注意若需要文件夹名需要自行处理
	 */
	public static List<String> queryFolderOfDirectoryName(String folderPath) {
		File folder = new File(folderPath);
		try{
			try(Stream<Path> sp=Files.walk(folder.toPath()).filter(Files::isDirectory)){
				return  sp.parallel().map(Path::toString).collect(Collectors.toList());
			}
		}catch(IOException e){}
		return Collections.emptyList();
	}

	/**
	 * 查看指定目录下的所有gif图片
	 * 
	 * @param folderPath
	 * @param fileExtName 文件的扩展名,例: .gif,包括dot,不能光写gif
	 * @return 返回的元素值是文件名+扩展名
	 */
	public static List<String> queryFolderOfFileName(String folderPath, String fileExtName) {
		File folder = new File(folderPath);
		try {
			try (Stream<Path> sp = Files.list(Paths.get(folder.getPath()))) {
				return  sp.parallel().map((Path p) -> {
					return p.getFileName().toString();
				}).filter(fn -> fn.toLowerCase().endsWith(fileExtName)).collect(Collectors.toList());
			}
		} catch (IOException e) {}
		return Collections.emptyList();
	}

	/**
	 * 
	 * @param url
	 *            处理的连接地址
	 * @param domain
	 *            域名,需要带有http[s]//
	 * @param defaultURL
	 *            失败时的默认值
	 * @return
	 */
	public static String getNativeURL(String url, String domain, String defaultURL) {
		if (isNotBlank(url) && url.startsWith(domain)) {
			try {
				URL nu = new URL(url);
				return nu.getFile();
			} catch (Exception e) {}
		}
		return defaultURL;
	}

	private static boolean isLetter(char c) {
		int k = 0x80;
		return c / k == 0;
	}

	/**
	 * 中文占2个,大写,小写,空格和数字占1个
	 * 
	 * @param string
	 *            null输出0
	 * @return
	 */
	public static int chineseStringLength(String string) {
		if (string == null) {
			return 0;
		}
		char[] c = string.toCharArray();
		int len = 0;
		for (int i = 0; i < c.length; i++) {
			len++;
			if (!isLetter(c[i])) {
				len++;
			}
		}
		return len;
	}
	
	/**
	 * 是否是ASCII字母或数字字符串
	 * 
	 * @param value 参数值
	 * @return 如果参数等于null或空字符返回false
	 */
	public static boolean isAlphaCharacter(String value){
		if(Commons.isNotBlank(value)){
			return value.getBytes().length == value.length();
		}
		return false;
	}
	
	/**
	 * 只保留参数中的字母和数字,其它的都删除
	 * 
	 * @param value 参数值
	 * @return 如果参数等于null或空字符返回空字符串
	 */
	public static String getAlphaNumberCharacter(String value){
		if(Commons.isNotBlank(value)){
			return value.replaceAll("[^a-zA-Z0-9]", "");
		}
		return "";
	}
	
	/**
	 * 格式化数字,例: 101800 => 101k
	 * {@link https://stackoverflow.com/questions/4753251/how-to-go-about-formatting-1200-to-1-2k-in-java}
	 * @param value 被格式化的数字
	 * @return
	 */
	public static String longNumbericFormat(long value) {
		// Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
		if (value == Long.MIN_VALUE){
			return longNumbericFormat(Long.MIN_VALUE + 1);
		}
		if (value < 0){
			return "-" + longNumbericFormat(-value);
		}
		if (value < 1000){
			return Long.toString(value); // deal with easy case
		}
		Entry<Long, String> e = suffixes.floorEntry(value);
		Long divideBy = e.getKey();
		String suffix = e.getValue();

		long truncated = value / (divideBy / 10); // the number part of the
												// output times 10
		boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
		return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
	}
	
	/**
	 * Http protocol是否是Https,是返回true
	 * 
	 * @param sitedomain 域名
	 * @return 若参数不包含Http protocol信息返回false, 若参数不可用返回false
	 */
	public static boolean isHttpsProtocol(String sitedomain){
		if(!Commons.isNotBlank(sitedomain)){
			return false;
		}
		try{
			String protocol = sitedomain.substring(0, sitedomain.indexOf(":"));
			return protocol.toLowerCase().equals("https");
		}catch(IndexOutOfBoundsException e){}
		return false;
	}
	
	/**
	 * 用单引号括住集合中的每一个字符串
	 * 
	 * @param params 参数必须是字符串,不适用于数字字符串集合
	 * @return 若参数为null或集合是空的返回null
	 */
	public static Optional<String> quoteSQLParameters(Collection<String> params) {
		if (null == params || params.isEmpty()) {
			return Optional.empty();
		}
		String data = String.join("','", params);
		return Optional.of(String.format("'%s'", data));
	}
	
	/**
	 * 获取文件的扩展名,使用ASF Commons IO FilenameUtils
	 * 
	 * @param filename 文件名或路径字符
	 * @return 失败会返回null或空字符串
	 */
	public static String getFileExtension(String filename) {
		return FilenameUtils.getExtension(filename);
	}
	
	/**
	 * 合并集合
	 * 
	 * @param <K>
	 * @param <V>
	 * @param baseMap           以此集合为基准
	 * @param extMap            在基准的集合上合并此集合
	 * @param remappingFunction 合并时的运作函数
	 * @return
	 */
	public static <K, V> Map<K, V> mergeMap(Map<K, V> baseMap, Map<K, V> extMap,BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
		if (null == extMap || extMap.isEmpty()) {
			return baseMap;
		}
		Map<K, V> data = new HashMap<>(baseMap);
		extMap.forEach((newKey, newVal) -> {
			data.merge(newKey, newVal, remappingFunction);
		});
		return data;
	}

	/**
	 * 使用InetAddress类的hashcode来实现
	 * 
	 * @param ipAddress 若参数不可用返回0
	 * @return 若有异常发生会返回-1;
	 */
	public static int ipHashcode(String ipAddress) {
		if (!Commons.isNotBlank(ipAddress)) {
			return 0;
		}
		try {
			InetAddress address = InetAddress.getByName(ipAddress);
			return address.hashCode();
		} catch (UnknownHostException e) {
			return -1;
		}
	}
	
	/**
	 * Whats the difference between Introspector vs just reflection?
	 * Introspection uses reflection to do its work, but works at a higher level
	 * than reflection.Reflection finds Java-level fields and methods;
	 * introspection finds JavaBeans-level properties and events.
	 * 
	 * @see https://stackoverflow.com/questions/6796187/java-introspection-object-to-map
	 * 
	 * @param obj
	 * @return
	 * @throws java.beans.IntrospectionException
	 * @throws java.lang.IllegalAccessException
	 * @throws java.lang.reflect.InvocationTargetException
	 */
	public static Map<String, Object> introspect(Object obj)throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Map<String, Object> result = new HashMap<>();
		BeanInfo info = Introspector.getBeanInfo(obj.getClass());
		for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
			Method reader = pd.getReadMethod();
			if (reader != null) {
				result.put(pd.getName(), reader.invoke(obj));
			}
		}
		return result;
	}

	/**
	 * 将字符串参数进行DES加密
	 * 
	 * @param secretString
	 *            公钥. 不可以为null,反之抛出NPE
	 * @param str
	 *            待加密的字符串. 不可以为null,反之抛出NPE
	 * @param failValue
	 *            加密失败时的操作
	 * @return
	 */
	public static String desEncrypt(String secretString, String str, Supplier<String> failValue) {
		Objects.requireNonNull(secretString);
		Objects.requireNonNull(str);
		String data;
		try {
			TripleDesCryp dcs = new TripleDesCryp(secretString);
			data = dcs.encrypt(str);
		} catch (Exception e) {
			data = failValue.get();
		}
		return data;
	}

	/**
	 * 对字符串参数进行DES解密
	 * 
	 * @param secretString
	 *            公钥. 不可以为null,反之抛出NPE
	 * @param str
	 *            待解密的字符串. 不可以为null,反之抛出NPE
	 * @param failValue
	 *            解密失败时的操作
	 * @return
	 */
	public static String desDecrypt(String secretString, String str, Supplier<String> failValue) {
		Objects.requireNonNull(secretString);
		Objects.requireNonNull(str);
		String data;
		try {
			TripleDesCryp dcs = new TripleDesCryp(secretString);
			data = dcs.decrypt(str);
		} catch (Exception e) {
			data = failValue.get();
		}
		return data;
	}

	/**
	 * 执行Stream的收集,使用Collectors.toMap
	 * 
	 * @param <T>
	 *            流的类型
	 * @param <K>
	 *            收集后的集合Key类型
	 * @param <V>
	 *            收集后的集合Key类型
	 * @param src
	 *            源流
	 * @param keyMaper
	 *            key的映射函数,注要重复性
	 * @param valueMapper
	 *            value的映射函数
	 * @return
	 */
	public static <T, K, V> Map<K, V> collectMap(Stream<T> src, Function<T, K> keyMaper, Function<T, V> valueMapper) {
		return src.collect(Collectors.toMap(keyMaper, valueMapper));
	}
    /**
     * 生成对象的json字符串
     * 
     * @param <T>
     * @param t
     * @return 
     */
    public static <T> String toJson(T t) {
        Objects.requireNonNull(t);
        return new Gson().toJson(t);
    }
    /**
     * 生成Map的json字符串
     * 
     * @param map
     * @return 
     */
    public static String toJson(Map<String,String> map) {
        if(null == map || map.isEmpty()){
            return "{}";
        }
        Type gsonType = new TypeToken<Map<String,String>>(){}.getType();
        return new Gson().toJson(map, gsonType);
    }
    /**
     * 将json字符串还原为Map
     * 
     * @param jsonString
     * @return 
     */
    public static Map<String,String> fromString(String jsonString){
        Objects.requireNonNull(jsonString);
        return new Gson().fromJson(jsonString, new TypeToken<Map<String, String>>() {}.getType());
    }
}
